#include "BlockManager.h"

#include <string.h>	// declares NULL
#include <stdio.h> // printf
#include <stdlib.h> // free

#ifdef _WIN32
#ifdef _DEBUG
   #include <crtdbg.h>
   #undef THIS_FILE
   static char THIS_FILE[] = __FILE__;
   #define new       new( _NORMAL_BLOCK, __FILE__, __LINE__)
   #define malloc(s) _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__)
#endif
#endif

// Constructor
BlockManager::BlockManager() {
	// initialize unique_id
	this->index = BN_new();
	this->todo_count = BN_new();
	this->block_size = BN_new();

	// blank out link list of blocks
	this->head = this->tail = NULL;
}

// Deconstructor
BlockManager::~BlockManager() {
	// clear out the current blocks
	clear();

	// free up the bignum resources
	BN_free(this->index);
	BN_free(this->todo_count);
	BN_free(this->block_size);
}

// initialize block manager with new block info
void BlockManager::init(char *todo_count, char *block_size, int timeout) {
	// start the index to hand out blocks at the beginning
	BN_dec2bn(&(this->index), "0");
	// set todo_count & block_size to the provided values
	BN_dec2bn(&(this->todo_count), todo_count);
	BN_dec2bn(&(this->block_size), block_size);

	this->timeout = timeout;

	// clear out the current blocks (if there are any)
	clear();
}

// clear out the current blocks
void BlockManager::clear() {
	Block *tmp;
	while(this->head != NULL) {
		tmp = this->head;
		this->head = this->head->next;

		// free up the BIGNUM unique_id
		BN_free(tmp->index);
		BN_free(tmp->count);
		delete tmp;
	}

	// blank out link list of blocks
	this->head = this->tail = NULL;
}

Block* BlockManager::get(void) {
	// get current time
	time_t curr = time(NULL);

	// hand out a new block, if there is a new block to hand out (if these two are not equal)
	if(BN_cmp(this->index, this->todo_count)) {
		BIGNUM *tmp = BN_new();

		// create new block
		Block *block = new Block;
		block->index = BN_new();
		block->count = BN_new();
		BN_copy(block->index, this->index);

		// find out if the num of todos left is less than the block size
		BN_sub(tmp, this->todo_count, this->index);
		if(BN_cmp(this->block_size, tmp) == 1) {
			// this is the last block, it's smaller than all the others
			// save the size left
			BN_copy(block->count, tmp);
		}
		else {
			// typical block_size handout
			BN_copy(block->count, this->block_size);
		}
		block->date_assigned = curr;
		block->complete = false;

		// adjust main index
		BN_add(this->index, this->index, block->count);
		// attach new block to link-list
		if(this->head == NULL) {
			// this is our first block
			block->prev = block->next = NULL;
			this->head = this->tail = block;
		}
		else {
			// otherwise, attach to end of link list
			this->tail->next = block;
			block->prev = this->tail;
			block->next = NULL;
			this->tail = block;
		}

		// free up the memory used by the temporary BIGNUM
		BN_free(tmp);
		
		return block;
	}
	// if no new blocks are available, go back and re-dispatch old blocks
	else {
		Block *slider = this->head;
		while(slider != NULL) {
			// Is this a block which is not yet complete?
			if(slider->complete == false) {
				// Has enough time passed to allow for re-dispatch?
				if((curr - slider->date_assigned) > this->timeout) {
					// Yes, we can redispatch this block
					slider->date_assigned = curr;
					return slider;
				}
			}
			// Merge together completed blocks while we are here
			else {
				// merge with prev block if it's similar
				if(slider->prev != NULL) {
					if(slider->prev->complete == true) {
						// merge with prev block
						BN_add(slider->prev->count, slider->prev->count, slider->count);
						slider->prev->next = slider->next;
						if(slider->next != NULL) {
							slider->next->prev = slider->prev;
						}
						// temporary pointer to the slider to free up memory
						Block *t_slider = slider;
						// move back one to the newly merged block
						slider = slider->prev;
						
						// free up the memory used by block
						delete t_slider;
					}
				}
			}

			slider = slider->next;
		}
	}

	// no blocks are availalble, some may be processing or this task could be finished
	return NULL;
}

// set a block as complete
bool BlockManager::set(char *index) {
	Block *t_slider;
	BIGNUM *tmp = BN_new();
	BN_dec2bn(&(tmp), index);

	// error checking: if no blocks available
	if(this->head == NULL) {
		// free up the memory used by the temporary BIGNUM
		BN_free(tmp);

		return false;
	}

	// search for the block
	Block *slider = this->head;
	while(slider != NULL) {
		if(!BN_cmp(slider->index, tmp)) {
			break;
		}
		slider = slider->next;
	}

	// error checking: block not found
	if(slider == NULL) {
		// free up the memory used by the temporary BIGNUM
		BN_free(tmp);

		return false;
	}

	// mark the block as complete
	slider->complete = true;

	// merge with prev block if it's similar
	if(slider->prev != NULL) {
		if(slider->prev->complete == true) {
			// merge with prev block
			BN_add(slider->prev->count, slider->prev->count, slider->count);
			slider->prev->next = slider->next;
			if(slider->next != NULL) {
				slider->next->prev = slider->prev;
			}
			if(slider == this->tail) {
				this->tail = slider->prev;
			}

			t_slider = slider;
			slider = slider->prev;

			// free up memory used by the merged block
			BN_free(t_slider->index);
			BN_free(t_slider->count);
			delete t_slider;
		}
	}

	// merge with next block if it's similar
	if(slider->next != NULL) {
		if(slider->next->complete == true) {
			// merge with next block
			BN_add(slider->next->count, slider->next->count, slider->count);
			slider->next->prev = slider->prev;
			if(slider->prev != NULL) {
				slider->prev->next = slider->next;
			}
			if(slider == this->head) {
				this->head = slider->next;
			}

			BN_free(slider->index);
			BN_free(slider->count);
			delete slider;
		}
	}

	// free up the memory used by the temporary BIGNUM
	BN_free(tmp);

	// maybe a merge occured, either way, the block is marked as complete
	return true;
}

// return if all blocks are complete
bool BlockManager::isComplete(void) {
	// error checking: if blocks have not been dispatched
	if(this->head == NULL) {
		return false;
	}

	// if the first block is complete
	if(this->head->complete) {
		// if the block contains a todo count equal to the goal
		if(!BN_cmp(this->head->count, this->todo_count)) {
			return true;
		}
	}

	return false;
}

// returns the number of todos completed (slower than isComplete)
char* BlockManager::getStatus() {
	BIGNUM *complete = BN_new();
	BN_dec2bn(&(complete), "0");

	// error checking: if blocks have not been dispatched
	if(this->head == NULL) {
		char *status = BN_bn2dec(complete);
		BN_free(complete);

		return status;
	}

	// otherwise loop through blocks and count completed
	Block *slider = this->head;
	while(slider != NULL) {
		if(slider->complete) {
			BN_add(complete, complete, slider->count);
		}

		slider = slider->next;
	}

	char *status = BN_bn2dec(complete);
	BN_free(complete);

	return status;
}